Copyright ã1995 by NeXT Computer, Inc.  All Rights Reserved.

DBFetchGroup

Inherits From:	Object

Declared In:	dbkit/DBFetchGroup.h

Class Description

A DBFetchGroup routes data from a DBRecordList to the user interface objects that display its contents.  It also routes flow in the reverse direction, when the user edits the displays.  A DBFetchGroup belongs to a DBModule; each DBModule has at least one DBFetchGroup.  A DBFetchGroup contains a set of DBAssociations; each maps one database property to be fetched (a DBExpression) to an element of the application program, usually an element of the user interface, such as a TextField, a DBImageView, or a column within a DBTableView.

If your application relies on the Database Kit's standard facilities, you will not need to make explicit use of DBFetchGroup.  In Interface Builder, you need only drag an instance of DBModule off the palette and make connections between it and elements of your user interface.  At run time, the necessary DBFetchGroups and their various DBAssociations will be created for you automatically.

A DBModule's prime (and perhaps only) fetch group is called its root fetch group.  The module may also require one or more subordinate fetch groups.  Whenever the expression being fetched traverses a one-to-many relationship, the DBModule requires separate DBRecordLists, each with its own DBFetchGroup.  The fetch groups are in a hierarchy that corresponds to the data being fetched.   

For example, suppose your application has a scrollable display of customers; for each customer there is a list of orders; for each order there's a list of items in the order.  As the user selects a customer, the order display must be updated to show that customer's orders.  As the user selects an order, the item display must be updated to show that order's line items.  The synchronization is managed by a set of three DBFetchGroups, each with its own DBRecordList.  The root DBFetchGroup manages data for the customer display.  Subordinate to it, a second DBFetchGroup keeps the order display in step with the currently selected customer.  And subordinate to that, a third DBFetchGroup keeps the line-item display in step with the currently selected order.

Whenever there's a fetch, the DBFetchGroup takes care of updating the display to reflect the data newly arrived in the record list.  Similarly, when the user edits a control, the DBFetchGroup updates the record list, and then notifies any other elements that may be displaying the same property.   The first fetch of a DBFetchGroup causes a setProperties:ofSource: message to be sent to its DBRecordList.

The DBFetchGroup also manages a second kind of user-interface state:  the current record and the current selection (which may be one record or several).  The notion of ªcurrent recordº exists because controls can display one value at a time, although a record list can contain many records.  The current record is the one displayed in a TextField or a DBImageView.  The fetch group remembers which record in the record list is the current record.  The designation of a current record can be changed by the user or under program control. 

Note:  The DBFetchGroup's current record and selected record list are independent of the cursor of a DBRecordSteam or DBRecordList.

Multiple Selection

In an object that can display a list of values, such as an NXBrowser or a DBTableView, the user can make a multiple selection.  Shift-click selects additional records without deselecting those already selected.  They don't have to be contiguous.  But when there is a multiple selection, no record is the current record, and subordinate displays keyed to the current record are cleared.

The DBFetchGroup relies on objects in the user interface (such as the DBTableView) to represent multiple selection to the user.  The DBFetchGroup will make use of multiple-selection information (as in deleteCurrentSelection), but does not manage it.  If your application needs to set a multiple selection, it should send the appropriate DBTableView one of its selection-setting messages.  Then, to keep the DBFetchGroup synchronized with change in selection at the DBTableView, it must send the following messages:

[[theTableView dataSource] tableViewDidChangeSelection:theTableView];

Instance Variables

    None declared in this class.

Method Types

Initializing	- initEntity:
- setName:

Reporting current context	- name
- module
- entity
- recordList
- currentRecord
- recordCount

Controlling current selection	- setAutoSelect:
- doesAutoSelect
- setCurrentRecord:
- clearCurrentRecord
- selectedRowAfter:
- redisplayEverything

Manipulating contents	- deleteCurrentSelection
- insertNewRecordAt:
- fetchContentsOf:usingQualifier:

Dealing with changes	- hasUnsavedChanges
- validateCurrentRecord
- saveChanges
- discardChanges

Using associations	- addExpression:
- makeAssociationFrom:to:
- takeValueFromAssociation:
- addAssociation:
- removeAssociation:

Using a delegate	- delegate
- setDelegate:

Instance Methods

addAssociation:

- addAssociation:newAssociation

Adds an association to the list of associations that govern the DBFetchGroup's selection of rows.  The argument newAssociation is a DBAssociation object.  Returns self.

addExpression:

- addExpression:newExpression

Adds the DBProperties-conforming object anExpression to the list of expressions that the DBFetchGroup will fetch from the database.  Typically, you add instances of DBExpression, although it's also possible to add property objects that are gotten from a database model. 

When you add an expression object to a DBFetchGroup, the DBFetchGroup ªownsº the object and is responsible for freeing it.  If you don't want to surrender control of your expression objects, you should create a copies of them (and pass the copies in invocations of this method).  If you're adding a model property, then you must copy it first (or, better, use a DBExpression to cover it).

Returns self.

clearCurrentRecord

- clearCurrentRecord

Deselects the currently selected record (or records), so that there is no selected record.  DBAssociations that may have been involved in the formerly selected records are notified of the change.  Returns self.  However, if there is no permission to change the rows of the DBFetchGroup's DBRecordList, the method has no effect and returns nil.

currentRecord

- (unsigned int)currentRecord

Returns the position (index number) of the current record in the DBFetchGroup's DBRecordList.

delegate

- delegate

Returns the DBFetchGroup's delegate.

See also:  - setDelegate

deleteCurrentSelection

- deleteCurrentSelection

Deletes the currently selected row (or rows) from the DBFetchGroup's DBRecordList.  Following the deletion, no rows are selected.  All DBAssociations are notified of the change.

Returns self.  However, if no rows were selected, or there is no permission to change the rows of the DBFetchGroup's DBRecordList, the method has no effect and returns nil.

discardChanges

- discardChanges

Terminates any editing changes currently in progress for this DBFetchGroup and recursively for any of its subordinate DBFetchGroups.  All the DBAssociations involved are notified so that they can update the display accordingly.  Returns self.

doesAutoSelect

- (BOOL)doesAutoSelect

Returns YES if autoselection is in effect.  When this flag has been set to YES, following each fetch through the DBFetchGroup, the first retrieved record is selected; following a delete, the first remaining record after the first deleted record is selected.  When the flag is NO, following fetch or delete, no record is selected.

entity

- entity

Returns the DBEntity to which the DBFetchGroup belongs.

fetchContentsOf:usingQualifier:

- fetchContentsOf:aSource usingQualifier:aQualifier

Replaces the content of the current DBRecordList by records fetched from the database.  Any editing in progress for this fetch group is terminated and changes are lost.  The argument aSource may be nil, in which case all records in the DBFetchGroup's entity are fetched.  If aSource is a DBValue containing NULL, the effect is to clear the DBRecordList without fetching any new records.

Alternatively, aSource may be a DBValue that specifies a relationship.  For example, suppose the relationship joins the entity called Department to the entity called Employees,  containing the employees belonging to each department.  The DBValue may contain a specific value for the property ªDepartment Numberº and also the entity to which it is joined (Employees).  Records will be fetched for all employees in the indicated department, using the key value of Department Number as a foreign key that qualifies the selection of records from Employees. 

The argument aQualifier is a DBQualifier that further restricts the records that will be fetched.

If the parent DBModule's delegate responds to fetchGroupWillFetch:, it is notified.  Similarly, after the fetch, if the DBModule's delegate responds to fetchGroupDidFetch:, it is a notified.  Provided the fetch is successful, the various DBAssociations are notified that the contents of their views has changed, so they can redraw themselves.  The current record index is set to 0 (the index of the first record).  Returns self.

hasUnsavedChanges

- (BOOL)hasUnsavedChanges

Returns YES if there are unsaved changes in this DBFetchGroups's DBRecordList, or in any of its subordinate DBRecordLists, and NO otherwise.

initEntity:

- initEntity:anEntity

Initializes an instance of DBFetchGroup.  The fetch group thus initialized will coordinate fetches for the owning DBModule from the DBEntity named anEntity.  Returns self.

insertNewRecordAt:

- (BOOL)insertNewRecordAt:(unsigned int)index

Instructs the DBFetchGroup's DBRecordList to insert a new record at the position indicated by index.  When index is negative, the method appends the new record (that is, inserts it at the end of the DBRecordList instance.

Returns YES if the DBRecordList is able to comply, and NO otherwise.  A NO return may arise if the application has no authorization to modify rows, if no records have been fetched, or if for any reason the DBRecordList returns NO.

If the DBFetchGroup has appointed a delegate and the delegate implements the method fetchGroup:didInsertRecordAt:, the method insertNewRecordAt: notifies the delegate.  The delegate may then fill in default values in the new record.

makeAssociationFrom:to:

- makeAssociationFrom:anExpr to:aView

Creates a new instance of DBAssociation for the destination DBFetchGroup.  The new association will link the DBExpression anExpr (an expression to be fetched) with the user interface object aView where the data is displayed.  Returns the new DBAssociation.

module

- module

Returns the DBModule instance to which the receiving DBFetchGroup belongs.

name

- (const char *)name

Returns the name of the DBFetchGroup.  Fetch groups that are created automatically are given names that match the names assigned in the model.  Fetch groups that were created by the application and initialized (for example, by initEntity:) remain unnamed until explicitly named by setName:.   

See also:  - setName: 

recordCount

- (unsigned int)recordCount

Returns the number of records in the DBFetchGroup's DBRecordList.

recordList

- recordList

Returns the DBRecordList instance that the receiving DBFetchGroup serves.

redisplayEverything

- redisplayEverything

Causes redisplay of all the fields governed by the DBFetchGroup's DBAssociations.  (The redisplay is prompted by sending all the DBAssociations a notification that the contents changed, and they respond in the same way as for any other change to their contents.)  As a side effect, this method checks the value of the current record index, and, if it is out of range, sets it to the index of the last record.  Returns self.

removeAssociation:

- removeAssociation:anAssociation

Removes the indicated association from the DBFetchGroup's list of associations.  Returns self.

saveChanges

- saveChanges

Saves changes made to any of the records governed by the receiving DBFetchGroup and any subordinate DBFetchGroups.  Before saving, the method terminates any editing that may have been in progress in the affected DBFetchGroups.  After saving, notifies the DBModule's delegate that the save took place.  Returns self.

selectedRowAfter:

- (unsigned int)selectedRowAfter:(unsigned int)previousRow

Returns the index of the first selected row that is located after the row specified by previousRow.  (Ordinarily, there is one selected row, also known as the current row.  But under some conditions the user may select multiple rows.  In that case, the return is the index of the first of them.)

If no row is selected, or the only selected rows occur earlier than previousRow, returns DB_NoIndex (which other methods interpret to mean ªafter the last recordº).

setAutoSelect:

- setAutoSelect:flag

Enables or disables autoselection, according to whether flag is YES or NO.  When autoselection is enabled, following each fetch through the DBFetchGroup, the first retrieved record is selected; following a delete, the first remaining record after the first deleted record is selected.  When flag is NO, following fetch or delete, no record is selected.

setCurrentRecord:

- setCurrentRecord:(unsigned int)newIndex

Sets the index of the current record to newIndex.  However, if the proposed value is less than the index of the first record, sets it to the first record; if the proposed value is greater than the last record, sets it the last record.  If executing this method changes the current record index, the DBFetchGroup's DBAssociations are notified that the selection changed (and can update the display accordingly).  Returns self.

setDelegate:

- setDelegate:anObject

Makes anObject the DBFetchGroup's delegate.  Returns self.

See also:  - delegate

setName:

- setName:(const char *)aName

Sets the name of the DBFetchGroup.  This method is invoked automatically when the fetch group is created, and your application will need to call it explicitly only if you explicitly create a new fetch group.  Returns self.

See also:  - name 

takeValueFromAssociation:

- takeValueFromAssociation:anAssociation

Takes a value from the part of the display governed by anAssociation, and inserts it in the corresponding position in the DBFetchGroup's DBRecordList.  The method then updates the display of other displayed fields that are governed by other DBAssociations belonging to the same DBFetchGroup.  Returns self.

validateCurrentRecord

- (BOOL)validateCurrentRecord

Returns YES if changes that have been proposed for the current record are valid (or if there is no current record).

The validation is done in two stages.  If there is a TextField editor for the field that changed, it reviews the changes first.  If the TextField editor says NO, that's the return.  If there is no text field editor, or the editor raises no objection to the change, the task of validation is passed to the DBModule's delegate.  (Each DBFetchGroup is owned by a DBModule.) Whatever the delegate returns becomes the return for this method.

Methods Implemented by the Delegate

fetchGroup:didInsertRecordAt:

- fetchGroup:fetchGroup didInsertRecordAt:(int)index

Notification that the DBFetchGroup fetchGroup has inserted a record in its DBRecordList at the position indicated by index.

fetchGroup:willDeleteRecordAt:

- fetchGroup:fetchGroup willDeleteRecordAt:(int)index

Invoked when the DBFetchGroup fetchGroup is about to delete the record at index from the DBRecordList.  The notification is sent by the DBFetchGroup method deleteCurrentSelection.  The notification gives the delegate a chance to note the fact (for example, to adjust its count of records, or to record information about the deleted record).  It doesn't matter what this method returns, since the calling method ignores the result.  The behavior of fetchGroup:willDeleteRecordAt: is simply a notification, without an opportunity to intercede.  But it's sent in advance of the actual deletion so that the delegate method canÐif desiredÐtake a look at the record before it's gone.

fetchGroup:willFailForReason:

- (DBFailureResponse)fetchGroup:fetchGroup willFailForReason:(DBFailureCode)code

Invoked when a failure is reported from the DBRecordList owned by fetchGroup.  The reason for failure is encoded as one of the following DBFailureCodes:

  DB_ReasonUnknown = 0
  DB_RecordBusy
  DB_RecordStreamNotReady
  DB_RecordHasChanged
  DB_RecordLimitReached
  DB_NoRecordKey
  DB_RecordKeyNotUnique
  DB_NoAdaptor
  DB_AdaptorError
  DB_TransactionError

The failure response that is returned must be one of the following constants, declared as type DBFailureResponse in the header file dbkit/enums.h:

DB_NotHandled	Displays a default attention panel but takes no other action

DB_Abort	Terminates the operation that encountered the error in its present state, and displays an attention panel

DB_Continue	Ignores the problem; permits the action to continue if possible.

If the delegate does not implement this method, the effect is the same as returning DB_NotHandled.

fetchGroup:willValidateRecordAt:

- (BOOL)fetchGroup:fetchGroup willValidateRecordAt:(int)index

Notification that the DBFetchGroup fetchGroup, while preparing to save its DBRecordList, has reached the point at which it would be appropriate to insert validity checks on the record indicated by index.  These might include checks for internal consistency between fields, or even checks that require a separate query to the database.  Validation for a single fields (ªIs this a valid phone number?º or ªIs this in a valid format for a telephone number?º) should be handled when a field editor notices that the user has changed a field's display (see the DBModule delegate method textWillChange:).

If your implementation of fetchGroup:willValidateRecordAt: returns YES (or if your delegate doesn't respond to that method), the record is treated as valid.  If it returns NO, the record is treated as invalid, the attempt to save records fails, and the user is notified by an attention panel.

fetchGroupDidFetch:

- fetchGroupDidFetch:fetchGroup

Invoked when fetchGroup has completed a fetch from the database.

fetchGroupDidSave:

- fetchGroupDidSave:fetchGroup

Invoked when fetchGroup has completed a save to the database.

fetchGroupWillChange:

- fetchGroupWillChange:fetchGroup

Invoked when fetchGroup is about to record change based on input from the user interface. 

fetchGroupWillFetch:

- fetchGroupWillFetch:fetchGroup

Invoked when fetchGroup is about to fetch data from the database.

fetchGroupWillSave:

- (BOOL)fetchGroupWillSave:fetchGroup

Invoked when fetchGroup is about to save the contents of the fetch group to the database.